package GestionPartida;

import java.util.Date;
import java.util.HashMap;
import java.util.Vector;

import ClasesBasicas.Partida;
import Enums.ColorFicha;
import Enums.TipoFicha;
import Exceptions.GameException;
import GestionUsuarios.Usuario;

/**
 * @author Grupo 04
 * 
 *         Clase que hereda de usuario pero tiene la logica de la IA del juego.
 */
public class Computadora extends Usuario {

	private Partida partida;
	protected String nick;
	private int modo;
	private final int SEGURO = 1;
	private final int MODERADO = 2;
	private final int AGRESIVO = 3;

	/**
	 * Contructor de la computadora
	 * 
	 * @param s
	 *            nombre de la computadora
	 * @param mod
	 *            modo de juego
	 */
	public Computadora(String s, int mod) {
		super(s, "");
		partida = null;
		if (modo > 3 || modo < 1) {
			modo = MODERADO;
		} else {
			modo = mod;
		}
	}

	public Partida getPartida() {
		return partida;
	}

	public void setPartida(Partida p) {
		partida = p;
	}

	public int getModo() {
		return modo;
	}

	public void setModo(int i) {
		if (i > 3 || i < 1) {
			modo = MODERADO;
		} else {
			modo = i;
		}
	}

	/**
	 * Calcula la casilla en la que colocara una ficha
	 * 
	 * @param nJugadas
	 *            numero de jugadas al futuro para las que se hara el calculo
	 * @return casilla en la que se colocara la ficha
	 */
	public Casilla calcularJugada(int nJugadas) {

		HashMap<Casilla, Nodo> jugadas = new HashMap<Casilla, Nodo>();
		Usuario[] usuarios = partida.getUsuarios();

		int turnoActual = 0;
		boolean encontrado = false;
		for (int i = 0; i < usuarios.length && !encontrado; i++) {
			if (usuarios[i] == this) {
				turnoActual = i;
				encontrado = true;
			}
		}

		for (int i = 0; i < partida.getTablero().size(); i++) {
			for (Casilla c : partida.getTablero().getCasillas()[i]) {
				if (c.containsCandidato(partida.getColor(usuarios[turnoActual]))) {
					jugadas.put(
							c,
							calcularJugadas(c, nJugadas - 1, partida
									.getTablero().copy(), turnoActual, usuarios));
				}
			}
		}
		Casilla c = calcularResultados(jugadas);
		return c;
	}

	/**
	 * Metodo recursivo para calcular las posibles jugadas de la computadora.
	 * Este metodo coloca una ficha en cada casilla posible y para cada una de
	 * esas jugadas se contruye un nuevo tablero y se calculan todas las jugadas
	 * para ese tablero, y asi sucesivamente.
	 * 
	 * @param cas
	 *            casilla en la que se ha colocado la ficha anterior
	 * @param nJugadas
	 *            numero de jugadas restantes
	 * @param t
	 *            copia del tablero anterior
	 * @param turnoActual
	 *            numero del turno del jugador actual
	 * @param usuarios
	 *            usuarios en la partida
	 * @return nodo que contiene el resultado numerico para la jugada (numero de
	 *         ganadas en positivo o numero de perdidas en negativo)
	 */
	private Nodo calcularJugadas(Casilla cas, int nJugadas, Tablero t,
			int turnoActual, Usuario[] usuarios) {

		int nGanados = 0;
		for (Casilla c : t.casillasGanadas(cas,
				partida.getColor(usuarios[turnoActual]))) {
			if (usuarios[turnoActual] == this) {
				nGanados++;
			} else {
				if (c.getFicha().getColor() == partida.getColor(this)) {
					nGanados--;
				}
			}
			c.getFicha().setColor(partida.getColor(usuarios[turnoActual]));
		}
		int turnoSiguiente = (turnoActual == usuarios.length - 1) ? 0
				: turnoActual + 1;
		Nodo n = new Nodo(nGanados);
		if (t.actualizarCandidatos()
				&& t.sePuedeColocar(partida.getColor(usuarios[turnoSiguiente]))) {
			for (int i = 0; i < t.size(); i++) {
				for (Casilla c : t.getCasillas()[i]) {
					if (c.containsCandidato(partida
							.getColor(usuarios[turnoSiguiente]))) {
						if (nJugadas <= 1) {
							if (usuarios[turnoActual] == this) {
								n.add(new Nodo(
										t.casillasGanadas(
												c,
												partida.getColor(usuarios[turnoSiguiente]))
												.size()));
							} else {
								int tmp = 0;
								for (Casilla ca : t.casillasGanadas(c, partida
										.getColor(usuarios[turnoSiguiente]))) {
									if (ca.getFicha().getColor()
											.equals(partida.getColor(this))) {
										tmp--;
									}
								}
								n.add(new Nodo(tmp));
							}
						} else {
							n.add(calcularJugadas(c, nJugadas - 1, t.copy(),
									turnoSiguiente, usuarios));
						}
					}
				}
			}
		}

		return n;
	}

	/**
	 * Metodo concha para calcular el resultado optimo de las jugadas.
	 * 
	 * @param jugadas
	 *            nodo raiz de todas las jugadas que se han calculado
	 * @return casilla en la que se colocara la ficha
	 */
	public Casilla calcularResultados(HashMap<Casilla, Nodo> jugadas) {
		int valor = Integer.MIN_VALUE;
		Casilla elegida = null;
		for (Casilla c : jugadas.keySet()) {
			Nodo n = jugadas.get(c);
			int res = 0;
			if (n.haySiguientes()) {
				res = calcularR(n);
			}
			int resultado = res + n.getElemento();
			if (resultado > valor) {
				valor = resultado;
				elegida = c;
			}
		}
		return elegida;
	}

	/**
	 * Calcula para cada nodo el resultado que se enviara al nivel superior
	 * (nodo anterior)
	 * 
	 * @param n
	 *            nodo actual
	 * @return resultado del nodo
	 */
	public int calcularR(Nodo n) {
		if (n.haySiguientes()) {
			Vector<Integer> res = new Vector<Integer>();
			for (Nodo nodo : n.getSiguientes()) {
				res.add(calcularR(nodo));
			}
			int resultado = 0;
			if (modo == AGRESIVO) {
				int tmp = 0;
				for (Integer ent : res) {
					if (ent > tmp) {
						tmp = ent;
					}
				}
				resultado = tmp + n.getElemento();
			} else {
				if (modo == MODERADO) {
					int tmp = 0;
					for (Integer ent : res) {
						tmp += ent;
					}
					tmp = tmp / res.size();
					resultado = tmp + n.getElemento();
				} else {
					if (modo == SEGURO) {
						int tmp = res.get(1);
						for (Integer ent : res) {
							if (ent < tmp) {
								tmp = ent;
							}
						}
						resultado = tmp + n.getElemento();
					}
				}
			}
			return resultado;
		} else {
			return n.getElemento();
		}
	}
}

/**
 * @author Grupo 04
 * 
 *         Clase nodo usada para guardar los valores de los resultados de las
 *         jugadas y un enlace a los resultados de todas las jugadas
 *         consequentes.
 */
class Nodo {

	private int elemento;
	private Vector<Nodo> siguientes;

	public Nodo(int e) {
		elemento = e;
		siguientes = new Vector<Nodo>();
	}

	public int getElemento() {
		return elemento;
	}

	public void setElemento(int c) {
		elemento = c;
	}

	public Vector<Nodo> getSiguientes() {
		return siguientes;
	}

	public Nodo getSiguiente(int i) {
		if (i < siguientes.size()) {
			return siguientes.get(i);
		} else {
			return null;
		}
	}

	public void add(int i, boolean b) {
		Nodo nodo = new Nodo(i);
		siguientes.add(nodo);
	}

	public void add(Nodo n) {
		siguientes.add(n);
	}

	public boolean haySiguientes() {
		return !siguientes.isEmpty();
	}
}
